Forms.php

<?php

namespace Phad\Test\Integration;

/**
 * This class appears to test both form compilation and form submission
 *
 * @notice Several of these tests incidentally test the submit target/redirect feature.
 */
class Forms extends \Phad\Tester {

    protected array $blogTableColumns = ['id'=>'INTEGER PRIMARY KEY','title'=>'VARCHAR(200)', 'body'=>'VARCHAR(2000)'];


    public function testMultiplePdataNodes(){
        $lildb = \Tlf\LilDb::sqlite();
        $pdo = $lildb->pdo();
        $phad = $this->phad();
        $items = [];
        $phad->handlers['item_initialized'] = function($ItemInfo) use (&$items){
            $items[] = $ItemInfo;
        };
        $phad->pdo = $pdo;
        $lildb->create('blog',['id'=>'integer', 'title'=>'varchar(90)', 'body'=>'varchar(90)']);
        $lildb->insert('blog',['id'=>1,'title'=>'title 1','body'=>'body 1']);
        $lildb->insert('blog',['id'=>2,'title'=>'title 2','body'=>'body 2']);
        $lildb->insert('blog',['id'=>3,'title'=>'title 3','body'=>'body 3']);


        $form = new \Phad\Item('Form/MultiPdata', $this->file('test/input/views/'), 
            ['id'=>2, 'phad'=>$phad]);
        $form->force_compile = true;

        $this->str_contains($form,
            "title 2"
        );
        // echo $form;
        // exit;

        // echo $form;
// echo "\n\n\n-----------\n\n";



        $form = new \Phad\Item('Form/MultiPdata', $this->file('test/input/views/'), 
            ['title'=>'title 3', 'phad'=>$phad]);
        $form->force_compile = true;

        /** @knownissue the type='default' p-data node does NOT work on forms when any other p-data nodes are present. If the issue is fixed, this should be changed to str_contains()
         */
        $this->str_contains($form,
            "title 3"
        );
        $this->str_not_contains($form,
            "title 2"
        );

        // echo $form;
        // exit;
        

        $form = new \Phad\Item('Form/MultiPdata', $this->file('test/input/views/'), 
            ['body'=>'body 1', 'phad'=>$phad]);
        $form->force_compile = true;

        $this->str_contains($form,
            "body 1"
        );
        $this->str_not_contains($form,
            "title 2"
        );
    }

    public function testDeleteItem(){
        $lildb = \Tlf\LilDb::sqlite();
        $pdo = $lildb->pdo();
        $phad = $this->phad();
        $items = [];
        $phad->handlers['item_initialized'] = function($ItemInfo) use (&$items){
            $items[] = $ItemInfo;
        };
        $phad->pdo = $pdo;
        $lildb->create('blog',['id'=>'integer', 'title'=>'varchar(90)']);
        $lildb->insert('blog',['id'=>1,'title'=>'title 1']);
        $lildb->insert('blog',['id'=>2,'title'=>'title 2']);
        $lildb->insert('blog',['id'=>3,'title'=>'title 3']);


        $form = new \Phad\Item('Form/Candelete', $this->file('test/input/views/'), 
            ['id'=>2, 'phad'=>$phad]);
        $form->force_compile = true;
        $out = $form->delete();

        echo $out;

        print_r($items[0]->submit_errors);

        $blogs = $lildb->select('blog');
        $this->compare(
            [ ['id'=>1,'title'=>'title 1'],
              ['id'=>3,'title'=>'title 3'],
            ],
            $blogs
        );

    }

    public function testErrorMessage(){
        \Phad\TestHeaderHelper::$headers = [];
        $pdo = $this->getPdo();
        $phad = $this->phad();
        $phad->router = $this->router();
        $phad->pdo = $pdo;

        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';

        $BlogRow = ['body'=>'smol body', 'title'=>''];
        $view = $phad->item('Form/SimpleBlogWithError', ['Blog'=>$BlogRow]);

        $out = $view->submit();

        $this->compare_lines(
            $out,
            <<<HTML
                <form action="" method="POST">
                    <div class="my-error-class">
                        <p>'title' failed validation for 'required:true'</p>
                        <p>'body' failed validation for 'minlength:50'</p>
                    </div>
                    <input type="text" name="title" maxlength="75" required value="">
                    <textarea name="body" maxlength="2000" minlength="50">smol body</textarea>
                </form>
            HTML,
        );
        
        $this->test('Headers empty');
            $this->compare(
                [],
                \Phad\TestHeaderHelper::$headers,
            );
    }

    
    public function testSubmitDocumentation(){
        \Phad\TestHeaderHelper::$headers = [];
        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';

        $ldb = \Tlf\LilDb::sqlite();
        $pdo = $ldb->pdo();
        
        $ldb->create('blog', $this->blogTableColumns);

        $phad = $this->phad();
        $phad->router = $this->router();
        $phad->pdo = $pdo;

        //if `id` were set, an UPDATE would be done instead.
        $BlogRow = ['title'=>'Fine Title', 'body'=>'I have to be at least 50 characters. But that isn\'t very long.'];
        $view = $phad->item('Form/SimpleBlogNoTarget', ['Blog'=>$BlogRow]);
        $out = $view->submit();
        $BlogRow['id'] = $pdo->lastInsertId();

        $headers = \Phad\TestHeaderHelper::$headers;
        //you can foreach(as $h){ header(...$h) }
        //@export_end(Forms.ManualSubmission)

        $this->test('Headers');
            $this->compare(
                // [['Location: https://localhost/', true]],
                [],
                $headers,
            );

        $this->test('Data Was Inserted');
            $this->compare(
                [$BlogRow],
                $ldb->query('SELECT * FROM blog')
            );
    }

    /**
     * @test overriding onSubmit()
     */
    public function testControllerOnSubmitDocumentation(){
        \Phad\TestHeaderHelper::$headers = [];
        $phad = new class() extends \Phad {
            public function onSubmit($ItemInfo, &$ItemRow){
                if ($ItemInfo->name!='Blog')return true;
                $ItemRow['slug'] = str_replace(' ', '-', strtolower($ItemRow['title']));
                return true;
            }
        };
        $pdo = $this->getPdo();
        $phad->exit_on_redirect = false;
        $phad->force_compile = true;
        $phad->item_dir = $this->file('test/input/views/');

        $phad->router = $this->router();
        $phad->pdo = $pdo;
        $phad->target_url = '/blog/{slug}/{id}/';
        $cols = $this->blogTableColumns;
        $cols['slug'] = 'VARCHAR(100)';
        $this->createTable($pdo, 'blog', $cols);

        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';

 
        $BlogRow = 
            [
            'title'=>'Fine Title', 
            'body'=>'I have to be at least fifty characters. That shouldn\'t be much of a problem, though it does require a bit more typing. And if I had something original to say, that would make it better.'
            ];
        /** anonymous class to use as our controller */
        /** passing our custom controller & submitting manually */
        $view = $phad->item('Form/SimpleBlogNoTarget', ['Blog'=>$BlogRow]);
        /** $BlogRow is just an array with 'title' & 'body' **/
        $output = $view->submit();
        
        $BlogRow['id'] = $pdo->lastInsertId();
        $BlogRow['slug'] = 'fine-title';

        $this->test('There should be no output');
            $this->handleDidPass(strlen(trim($output))===0);

        $this->test('Headers');
            $this->compare(
                [['Location: https://localhost/blog/fine-title/'.$BlogRow['id'].'/', true]],
                \Phad\TestHeaderHelper::$headers,
            );

        $this->test('Data Was Inserted');
            $this->compare(
                $BlogRow,
                $this->queryOne($pdo, 'SELECT * FROM blog'),
            );
    }

    public function testWithInlineOnSubmit(){
        \Phad\TestHeaderHelper::$headers = [];
        $pdo = $this->getPdo();
        $phad = $this->phad();
        $phad->router = $this->router();
        $phad->pdo = $pdo;
        $phad->target_url = '/blog/{slug}/{id}/';
        $cols = $this->blogTableColumns;
        $cols['slug'] = 'VARCHAR(100)';
        $this->createTable($pdo, 'blog', $cols);

        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';

 
        $BlogRow = 
            [
            'title'=>'Fine Title', 
            'body'=>'I have to be at least fifty characters. That shouldn\'t be much of a problem, though it does require a bit more typing. And if I had something original to say, that would make it better.'
            ];

        $view = $phad->item('Form/BlogWithOnSubmit',['Blog'=>$BlogRow]);
        $output = $view->submit();
        
        $BlogRow['id'] = $pdo->lastInsertId();
        $BlogRow['slug'] = 'fine-title';

        $this->test('Backend Prop is removed');
            $this->str_not_contains($phad->item('Form/BlogWithOnSubmit', $BlogRow), 'type="backend"');

        $this->test('<onsubmit> tag was removed');
            $this->str_not_contains($phad->item('Form/BlogWithOnSubmit', $BlogRow), '<onsubmit>');

        $this->test('There should be no output');
            $this->handleDidPass(strlen(trim($output))===0);

        $this->test('Headers');
            $this->compare(
                [['Location: https://localhost/blog/fine-title/'.$BlogRow['id'].'/', true]],
                \Phad\TestHeaderHelper::$headers,
            );

        $this->test('Data Was Inserted');
            $this->compare(
                $BlogRow,
                $this->queryOne($pdo, 'SELECT * FROM blog'),
            );
    }
    public function testInsertWithValidOption(){
        \Phad\TestHeaderHelper::$headers = [];
        $pdo = $this->getPdo();
        $phad = $this->phad();
        $phad->pdo = $pdo;
        $phad->router = $this->router();
        $cols = $this->blogTableColumns;
        $cols['category'] = 'VARCHAR(100)';
        $this->createTable($pdo, 'blog', $cols);

        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';

        $BlogRow = ['title'=>'Police Brutality super sucks', 'body'=>'Its been well documented that police brutality is a reality, yet there\'s still overwhelming controversy about what we should do with our society. Some say: Let the police to what they gotta do. Others say: Lets rethink this society that\'s built upon punishing people who misbehave & actually help people instead. Mayb ewe could redirect some of the MASSIVE tax dollars that go to police departments to actually help someone. That\'s me. I say that. - Reed',
            'category'=>'Police Brutality'];

        $view = $phad->item('Form/BlogWithCategories', ['Blog'=>$BlogRow]);
        $out = $view->submit();

        
        $BlogRow['id'] = $pdo->lastInsertId();

        $this->test('There should be no output');
            $this->handleDidPass(strlen(trim($out))===0);

        $this->test('Headers');
            $this->compare(
                [['Location: https://localhost/blog/'.$BlogRow['title'].'/', true]],
                \Phad\TestHeaderHelper::$headers,
            );

        $this->test('Data Was Inserted');
            $this->compare(
                $BlogRow,
                $this->queryOne($pdo, 'SELECT * FROM blog'),
            );
    }

    public function testUpdateValid(){
        \Phad\TestHeaderHelper::$headers = [];
        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';
        $ldb = \Tlf\LilDb::sqlite();
        $phad = $this->phad();
        $phad->router = $this->router();
        $phad->pdo = $ldb->pdo();
        $ldb->create('blog', $this->blogTableColumns);

        $BlogRow = ['id'=>0, 'title'=>'Fine Title', 'body'=>'I have to be at least fifty characters. That shouldn\'t be much of a problem, though it does require a bit more typing. And if I had something original to say, that would make it better.'];
        $ldb->insert('blog', $BlogRow);
        $UpdatedBlog = $BlogRow;
        $UpdatedBlog['title'] = 'New Title';

        $view = $phad->item('Form/SimpleBlog',['Blog'=>$UpdatedBlog]);


        $out = $view->submit();

        $this->test('Headers');
            $this->compare(
                [['Location: https://localhost/blog/'.$UpdatedBlog['title'].'/', true]],
                \Phad\TestHeaderHelper::$headers,
            );

        $this->test('Data Was Updated');
            $this->compare(
                [$UpdatedBlog],
                $ldb->query('SELECT * FROM blog')
            );
        
        $this->test('No Output Present');
            $this->compare(
                '',
                trim($out)
            );
    }

    public function testInsertValid(){
        \Phad\TestHeaderHelper::$headers = [];
        $phad = $this->phad();
        $phad->exit_on_redirect = false;
        $phad->pdo = $this->getPdo();
        $phad->router = $this->router();


        $BlogRow = ['title'=>'Fine Title', 'body'=>'I have to be at least fifty characters. That shouldn\'t be much of a problem, though it does require a bit more typing. And if I had something original to say, that would make it better.'];

        $view = $phad->item('Form/SimpleBlog', ['Blog'=>$BlogRow]);
        $this->createTable($phad->pdo, 'blog', $this->blogTableColumns);

        $_SERVER['HTTP_HOST'] = 'localhost';
        $_SERVER['HTTPS'] = 'non-empty-value';


        $out = $view->submit();
        
        $BlogRow['id'] = $phad->pdo->lastInsertId();

        $this->test('There should be no output');
            $this->handleDidPass(strlen(trim($out))===0);
        var_dump($out);


        $this->test('Headers');
            $this->compare(
                [['Location: https://localhost/blog/'.$BlogRow['title'].'/', true,]],
                \Phad\TestHeaderHelper::$headers,
            );

        $this->test('Data Was Inserted');
            $this->compare(
                $BlogRow,
                $this->queryOne($phad->pdo, 'SELECT * FROM blog'),
            );
    }

    /**
     * @test that a partially filled in form is returned when submission fails.
     */
    public function testSubmitInvalid(){
        $phad = $this->phad();
        $phad->pdo = $this->pdo();
        // $phad->pdo = new \PDO('sqlite:memory:?cache=w');
        // $phad->force_compile = false;
        $this->createTable($phad->pdo, 'blog', $this->blogTableColumns);


        // View contains: 
        // <textarea name="body" maxlength="2000" minlength="50"></textarea>
        $Blog = ['title'=>'Fine Title', 'body'=>'body too short'];
        $view = $phad->item('Form/SimpleBlog', ['Blog'=>$Blog]);


        // should fail because body is less than 50 chars
        $out = $view->submit();


        echo $out;
        
        $this->test('Form cleaned up');
            $this->str_not_contains($view, ['item=']);

        $this->test('Submitted Blog Content');
            $this->str_contains($out, 'value="'.$Blog['title'].'"');
            $this->str_contains($out, '>'.$Blog['body'].'</textarea>');
    }

    public function testDisplayWithNoObject(){
        $view = $this->item('Form/SimpleBlog');

        // $Blog = ['title'=>'Fine Title', 'body'=>'body too short'];
        $out = $view->html_with_no_data();
        // $Blog = (object)$Blog;

        $out = $view->html();
        echo $out;
        
        $this->test('Form cleaned up');
            $this->str_not_contains($out, ['item=']);
        $this->test('Target Attribute Removed');
            $this->str_not_contains($out, 'target="');

        $this->test('Submitted Blog Content');
            $this->str_contains($out, 'value=""');
            $this->str_contains($out, '></textarea>');

    }

    /**
     * @test getting each selectable-option from a form's compiled view
     */
    public function testHasSelectOptions(){
        $view = $this->item('Form/BlogWithCategories');
        $ItemInfo = $view->info();

        $expect = [
            'title' => 
            [
                'type' => 'text',
                'maxlength' => '75',
                'tagName' => 'input',
            ],
            'body' => 
            [
                'maxlength' => '2000',
                'minlength' => '50',
                'tagName' => 'textarea',
            ],
            'category'=>
            [
                'tagName'=> 'select',
                'options'=>[
                    "social-justice",
                    "policy",
                    "Police Brutality",
                    "Election",
                ],
            ],
            'id'=>
            [
                'tagName'=>'input',
                'type'=>'hidden',
            ]
        ];
        $this->compare($expect, $ItemInfo->properties);
    }

    /**
     * @test getting info about properties from the compiled view.
     */
    public function testHasPropertiesData(){
        $view = $this->item('Form/SimpleBlog');
        $ItemInfo = $view->info();

        $expect = [
            'title' => 
            [
                'type' => 'text',
                'maxlength' => '75',
                'tagName' => 'input',
            ],
            'body' => 
            [
                'maxlength' => '2000',
                'minlength' => '50',
                'tagName' => 'textarea',
            ],
            'id'=>
            [
                'tagName'=>'input',
                'type'=>'hidden',
            ]
        ];
        $this->compare($expect, $ItemInfo->properties);
    }

    public function item($name, $args=[]){
        $phad = new \Phad();
        $args['phad'] = $phad;
        $item = new \Phad\Item($name, $this->file('test/input/views/'),$args);
        $item->force_compile = true;
        return $item;
    }
    public function phad($idk=null){
        $phad = new \Phad();
        $phad->exit_on_redirect = false;
        $phad->force_compile = true;
        $phad->item_dir = $this->file('test/input/views/');
        return $phad;
    }

    public function pdo(){
        return new \PDO("sqlite::memory:");
    }

    public function router(){
        return new \Lia\Addon\Router();
    }

}

namespace Phad;

class TestHeaderHelper{
    static $headers = [];
}

function header(...$args){
    TestHeaderHelper::$headers[] = $args;
}